#ifndef __DEPOT_H__
#define __DEPOT_H__

#include <typeinfo>
#include <string>
#include <map>
#include <sstream>
#include "Singleton.h"
#include "Manager.h"

namespace RayFrame
{
  class Bucket;

  /** Depot is a object container which can automatically destroy object
     stored in it when it's not needed any more. Depot keeps buckets,
     which keeps key-value pairs. In most cases you should use helper
     macros other than using Depot class directly.

     Depot is different from RayFrame::Manager since it doesn't care about the
     type of pointer, and it's a singleton. This means all types of data
     are stored in a single class.
     **/
  class Depot: public Singleton<Depot>
  {
  protected:
    typedef std::map<STRING, Bucket*>  MapDepot;
    typedef MapDepot::iterator         MapDepotIter;

    MapDepot  m_depot;
  public:
    Depot();
    ~Depot();

    /// Add a new bucket to the depot
    void AddDepotBucket(Bucket* bucket);

    /// Remove a bucket
    void RemoveDepotBucket(Bucket* bucket);

    DECL_SINGLETON_DLL(Depot);
  };

  class IDestroyer
  {
  public:
    IDestroyer(){};
    virtual ~IDestroyer(){};
  };

  /// Destroyer is in charge of deleting the data pointer properly.
  template<typename T>
  class Destroyer: public IDestroyer
  {
  private:
    T* m_data;
  public:
    Destroyer(){ m_data = NULL; }

    Destroyer(T* value){ SetData(value); }

    void SetData(T* value)
    {
      m_data = value;
      // TODO: delete this line
      //const_cast <std::type_info *> ( &( typeid ( value )) );
    }

    ~Destroyer(){ delete m_data; }

    T* GetData()
    {
      if(!m_data)
        return false;
      return m_data;
    };
  };

  /** Container is used to store the data pointer and Destroyer. It also
      keep the RIIT information of the data pointer so that we are able
      to keep type safety. **/
  class Container
  {
  private:
    void*       m_pData;         // Pointer to the data held in this object instance
    IDestroyer* m_pDestroyer;    // Pointer to destruction factory, class that will clear out our data
    std::type_info*  m_pTypeName;     // Type name saved off so we can do run-time type check
  public:
    Container()
    {
      m_pData = NULL;
      m_pDestroyer = NULL;
      m_pTypeName = NULL;
    }

    ~Container()
    {
      // Delete the destroyer object which will delete the data stored in this
      // object instance:
      if(m_pDestroyer)
      {
        delete m_pDestroyer;
        m_pDestroyer = NULL;
      }
    }

    /// Return type id of this object instance
    std::type_info* GetTypeID ()
    {
      return m_pTypeName;
    }

    /// Checks whether this object instance is empty:
    bool IsEmpty ()
    {
      if ( m_pData )
        return false;
      else
        return true;
    }

    /** Check whether the given type is the same as the object's data
        type.

        Returns: true if the type is the same, false if otherwise. **/
    template <class DataType> bool CheckType()
    {
      if ( (*m_pTypeName) == typeid ( DataType ) )
        return true;
      else
        return false;

    }

    /** Retrieve data from this object instance in the appropriate
        dataValue type (DataType). Will return false if either the type
        given is not the same as the type of this particular object
        instance or otherwise failed: **/
    template <class DataType>
    DataType* GetData ()
    {
      // Check whether the data type is the same as this object entry's
      // type:
      if ( (*m_pTypeName) != typeid ( DataType ) )
      {
        return false;
      }

      // Cast the data stored into the appropriate type:
      DataType* pTmpData = static_cast <DataType *> ( m_pData );

      // Retrieve the data value:
      return pTmpData;
    }

    /** Retrieve data from this object in the appropriate data type
        (DataType).  Note that this version does not check whether the
        data type of the parameter is the same as the data type of the
        data value stored in this object, and will attempt to cast it to
        the appropriate value. Use at your own risk: **/
    template <class DataType>
    DataType* GetDataNotTypeSafe ()
    {
      // Cast the data stored into the given type:
      DataType* pTmpData = static_cast <DataType*> ( m_pData );

      // Retrieve the data value:
      return pTmpData;
    }

    /** Assign some data to this object. Note that the type info Will
        also be assigned. If any other data was stored previously in
        this object, it will be deleted. See the comment on top of the
        class declaration about data assignment for some
        precautions. **/
    template <class DataType>
    bool SetData( DataType* const dataValue )
    {
      // First make sure to delete any data previously stored in this object:
      if ( m_pData )
      {
        delete( m_pDestroyer );
        m_pDestroyer = NULL;
      }

      // Create new storage for the data value:
      DataType* pData = dataValue;

      m_pData = pData;

      // Save the type information for this object:
      m_pTypeName = const_cast <std::type_info *> ( &( typeid ( DataType )) );

      // Create a destroyer for the data for this data object:
      m_pDestroyer = new Destroyer<DataType> ( pData );

      return true;
    }
  };

  /** Bucket stores key-value pairs. pointer passed into bucket will
      be deleted automatically **/
  class Bucket
  {
    friend class Depot;
  private:
    typedef std::map<STRING, Container*> MapData;
    typedef MapData::iterator       MapDataIter;

    STRING      m_name;
    MapData     m_data;
    IDestroyer* m_destroyer;

  public:
    Bucket(STRING name, IDestroyer* destroyer)
    {
      m_name = name;
      m_destroyer = destroyer;
      Depot::InstanceRef().AddDepotBucket(this);
    };

    virtual ~Bucket()
    {
      ClearData();
    }

    STRING GetName(){ return m_name; };

    void ClearData()
    {
      for( MapDataIter iter = m_data.begin();
        iter != m_data.end();
        iter ++)
      {
        delete iter->second;
      }
      m_data.clear();
    }

    template<typename T>
    bool InsertData(STRING& name, T* data)
    {
      static int count = 0;
      std::stringstream ss;

      if( m_data.find(name) != m_data.end() )
        return false;

      // auto generate a name
      if(name.empty())
      {
        ss << "_default_" << count++;
        name = ss.str();
      }

      Container* container = new Container();
      m_data.insert( std::make_pair(name, container) );
      container->SetData<T>(data);

      return true;
    }

    template<typename T>
    T* SearchData(STRING name)
    {
      MapDataIter iter = m_data.find(name);
      if( iter == m_data.end() )
        return NULL;

      if( iter->second->IsEmpty() )
        return NULL;

      if( !iter->second->CheckType<T>() )
        return NULL;

      return iter->second->GetData<T>();
    }

    template<typename T>
    bool Has(STRING name)
    {
      return m_data.find(name) != m_data.end();
    }

    template<typename T>
    bool Remove(STRING name)
    {
      MapDataIter iter = m_data.find(name);
      if(iter == m_data.end())
        return false;

      delete iter->second;
      m_data.erase(iter);
      return true;
    }

    template<typename T>
    STRING SearchName(T* data)
    {
      bool found = false;

      MapDataIter iter;
      for( iter = m_data.begin();
        iter != m_data.end();
        iter++)
      {
        Container *containter = iter->second;
        if( !containter->CheckType<T>() )
          return EMPTY_STRING;

        if( containter->GetData<T>() == data )
        {
          found = true;
          break;
        }
      }

      if(found)
        return iter->first;
      else
        return EMPTY_STRING;
    }
  };
};
/// Definition macro for a bucket
#define DECL_BUCKET_TYPE(x, T) \
class x: public Bucket, public Singleton<x> \
  {                           \
  public:                     \
  x();                      \
  DECL_SINGLETON(x);        \
};                          \
  void Clear##T (); \
  void Insert##T (STRING name, T* value); \
  T*   Search##T (STRING name); \
  bool Has##T (STRING name); \
  bool Remove##T (STRING name); \
  STRING SearchName##T (T* data);

/// Implementation macro of a bucket
#define IMPL_BUCKET_TYPE(n, x, T) \
  n::x::x():Bucket(#x, new Destroyer<T>) {} \
  void n::Clear##T () { x::Instance()->ClearData(); };  \
  void n::Insert##T (STRING name, T* value) \
  {   \
    if(x::Instance()->InsertData<T>(name, value)) \
      Logger::InstanceRef().Write(LogNormal, (STRING(#T) + STRING(" created: \"%s\".")).c_str(), name.c_str()); \
    else  \
      Logger::InstanceRef().Write(LogHigher, (STRING(#T) + STRING(" creation failed: \"%s\".")).c_str(), name.c_str()); \
  }; \
  T*   n::Search##T (STRING name){ return x::Instance()->SearchData<T>(name); }; \
  bool n::Has##T (STRING name){ return x::Instance()->Has<T>(name); }; \
  bool n::Remove##T (STRING name) \
  { \
    if(x::Instance()->Remove<T>(name)) \
    { \
      Logger::InstanceRef().Write(LogNormal, (STRING(#T) + STRING(" removed: \"%s\".")).c_str(), name.c_str()); \
      return true; \
    } \
    else \
    { \
      Logger::InstanceRef().Write(LogNormal, (STRING(#T) + STRING(" remove failed: \"%s\".")).c_str(), name.c_str()); \
      return false; \
    }; \
  } \
  STRING n::SearchName##T (T* data){ return x::Instance()->SearchName<T>(data); } \
  IMPL_SINGLETON(x);

#endif
